﻿//++
//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
// Module Name:
//
//  UpdateItemTask.cs
//    
// Abstract:
//
//  This module implements the UpdateItemTask class.
//
//--

using System;
using System.Diagnostics;
using System.Globalization;
using Microsoft.OfficeCommunicationsServer.Applications.Common;

namespace Microsoft.EnhancedReminders.Scheduler
{
    /// <summary>
    /// The base class for tasks that update mailbox items via EWS.
    /// </summary>
    internal abstract class UpdateItemTask : ExchangeTask
    {
        // Changes to be made
        protected ItemChangeType[] _changes;

        /// <summary>
        /// ctor
        /// </summary>
        /// <param name="taskContext">Task context</param>
        /// <param name="client">WCF proxy for communicating with EWS</param>
        /// <param name="impersonation">Exchange impersonation info</param>
        /// <param name="emailAddress">E-mail address of the user associated with this request</param>
        protected UpdateItemTask(TaskContext taskContext, ExchangeServicePortType client, ExchangeImpersonationType impersonation, string emailAddress)
            : base(taskContext, client, impersonation, emailAddress)
        {
        }

        /// <summary>
        /// Requests the change associated with this task via EWS.
        /// </summary>
        internal override void Begin()
        {
            Debug.Assert(_changes != null, "_changes != null");
            Debug.Assert(_changes.Length > 0, "_changes.Length > 0");

            Logger.WriteTraceVerbose(Context, "UpdateItemTask.Begin");

            UpdateItemType updateItem = new UpdateItemType();
            updateItem.ConflictResolution = ConflictResolutionType.AutoResolve;
            updateItem.MessageDisposition = MessageDispositionType.SaveOnly;
            updateItem.MessageDispositionSpecified = true;
            updateItem.SendMeetingInvitationsOrCancellations = CalendarItemUpdateOperationType.SendToNone;
            updateItem.SendMeetingInvitationsOrCancellationsSpecified = true;
            updateItem.ItemChanges = _changes;

            UpdateItemRequest updateItemRequest = new UpdateItemRequest();
            updateItemRequest.UpdateItem = updateItem;
            updateItemRequest.ExchangeImpersonation = _impersonation;
            updateItemRequest.RequestServerVersion = ExchangeUsers.RequestServerVersion;

            try
            {
                // Update the specified item via EWS
                _client.BeginUpdateItem(updateItemRequest, OnUpdateItemCompleted, null);
            }
            catch (Exception ex)
            {
                HandleAccessError(ex, "BeginUpdateItem failed.");
            }
        }

        /// <summary>
        /// Handles completion of the EWS request to change the mailbox item associated with this task.
        /// </summary>
        /// <param name="asyncResult">Status of the asynchronous operation</param>
        private void OnUpdateItemCompleted(IAsyncResult asyncResult)
        {
            Logger.WriteTraceVerbose(Context, "UpdateItemTask.OnUpdateItemCompleted");

            UpdateItemResponse updateItemResponse = GetResponse<UpdateItemResponse>(asyncResult, _client.EndUpdateItem, "EndUpdateItem failed.");

            if (updateItemResponse == null)
            {
                return;
            }

            ResponseMessageType responseMessage = updateItemResponse.UpdateItemResponse1.ResponseMessages.Items[0];
            Debug.Assert(responseMessage != null, "responseMessage != null");

            Exception error = null;

            if (responseMessage.ResponseCode == ResponseCodeType.NoError)
            {
                Logger.WriteTraceInformation(Context, "Item updated.");
            }
            else
            {
                Logger.WriteTraceError(Context, "Failed to update item. Response code: {0}", responseMessage.ResponseCode);
                error = new ExchangeTaskFailedException(Context, responseMessage.ResponseCode, "Failed to update item.");
            }

            RaiseCompleted(new TaskCompletedEventArgs(_taskContext, error));
        }

        /// <summary>
        /// Creates the change description for changing the specified extended property with the specified data.
        /// </summary>
        /// <param name="path">Path to the extended property to be changed</param>
        /// <param name="value">Value for the extended property</param>
        /// <returns>
        /// The change description.
        /// </returns>
        internal static SetItemFieldType CreateItemChangeDescription(PathToExtendedFieldType path, string value)
        {
            Debug.Assert(path != null, "path != null");
            Debug.Assert(value != null, "value != null");

            ExtendedPropertyType property = new ExtendedPropertyType();
            property.ExtendedFieldURI = path;
            property.Item = value;

            ItemType item = new ItemType();
            item.ExtendedProperty = new ExtendedPropertyType[] { property };

            SetItemFieldType itemChangeDescription = new SetItemFieldType();
            itemChangeDescription.Item = property.ExtendedFieldURI;
            itemChangeDescription.Item1 = item;

            return itemChangeDescription;
        }
    }

    /// <summary>
    /// This handles the task of deleting a reminder from a mailbox item via EWS.
    /// </summary>
    internal class DeleteRemindersTask : UpdateItemTask
    {
        /// <summary>
        /// ctor
        /// </summary>
        /// <param name="taskContext">Task context</param>
        /// <param name="client">WCF proxy for communicating with EWS</param>
        /// <param name="impersonation">Exchange impersonation info</param>
        /// <param name="emailAddress">E-mail address of the user associated with this request</param>
        /// <param name="itemIds">Mailbox item IDs from which to delete the reminders</param>
        internal DeleteRemindersTask(TaskContext taskContext, ExchangeServicePortType client, ExchangeImpersonationType impersonation, string emailAddress, ItemIdType[] itemIds)
            : base(taskContext, client, impersonation, emailAddress)
        {
            Debug.Assert(itemIds != null, "itemIds != null");
            Debug.Assert(itemIds.Length > 0, "itemIds.Length > 0");
            
            _changes = new ItemChangeType[itemIds.Length];

            for (int i = 0; i < itemIds.Length; i++)
            {
                _changes[i] = CreateItemChangeForDeletingReminder(itemIds[i]);
            }
        }

        /// <summary>
        /// Create the item change description for deleting a rules-based reminder from the specified item.
        /// </summary>
        /// <param name="itemId">The item to be updated</param>
        /// <returns>
        /// The item change.
        /// </returns>
        private static ItemChangeType CreateItemChangeForDeletingReminder(ItemIdType itemId)
        {
            Debug.Assert(itemId != null, "itemId != null");

            DeleteItemFieldType itemChangeDescriptionReminder = new DeleteItemFieldType();
            itemChangeDescriptionReminder.Item = ExchangeHelpers.GetExtendedPropertyPathForReminder();

            DeleteItemFieldType itemChangeDescriptionReminderRuleId = new DeleteItemFieldType();
            itemChangeDescriptionReminderRuleId.Item = ExchangeHelpers.GetExtendedPropertyPathForReminderRuleId();

            DeleteItemFieldType itemChangeDescriptionJoinNowRuleId = new DeleteItemFieldType();
            itemChangeDescriptionJoinNowRuleId.Item = ExchangeHelpers.GetExtendedPropertyPathForJoinNowRuleId();

            ItemChangeType itemChange = new ItemChangeType();
            itemChange.Item = itemId;
            itemChange.Updates = new ItemChangeDescriptionType[] { itemChangeDescriptionReminder, 
                itemChangeDescriptionReminderRuleId, itemChangeDescriptionJoinNowRuleId };

            return itemChange;
        }
    }

    /// <summary>
    /// This handles the task of setting a reminder on a mailbox item via EWS.
    /// </summary>
    internal class SetReminderTask : UpdateItemTask
    {
        /// <summary>
        /// ctor
        /// </summary>
        /// <param name="taskContext">Task context</param>
        /// <param name="client">WCF proxy for communicating with EWS</param>
        /// <param name="impersonation">Exchange impersonation info</param>
        /// <param name="emailAddress">E-mail address of the user associated with this request</param>
        /// <param name="itemId">The item on which to set the reminder</param>
        /// <param name="reminderRule">Reminder rule that matches the specified item</param>
        /// <param name="joinNowRule">JoinNow rule that matches the specified item</param>
        /// <param name="timeZoneInfo">Timezone associated with the calendar rules</param>
        internal SetReminderTask(TaskContext taskContext, ExchangeServicePortType client, ExchangeImpersonationType impersonation, string emailAddress, ItemIdType itemId, NotificationRuleItem reminderRule, NotificationRuleItem joinNowRule, TimeZoneInfo timeZoneInfo)
            : base(taskContext, client, impersonation, emailAddress)
        {
            Debug.Assert(itemId != null, "itemId != null");
            Debug.Assert(reminderRule != null || joinNowRule != null, "reminderRule != null || joinNowRule != null");

            _changes = new ItemChangeType[] { CreateItemChangeForUpdatingReminder(itemId, reminderRule, joinNowRule, timeZoneInfo) };
        }

        /// <summary>
        /// Create the item change description for adding a rules-based reminder derived from the specified rules to the 
        /// specified calendar item.
        /// </summary>
        /// <param name="itemId">The item to be updated</param>
        /// <param name="reminderRule">Reminder rule that matches the specified item</param>
        /// <param name="joinNowRule">JoinNow rule that matches the specified item</param>
        /// <param name="timeZoneInfo">Timezone associated with the calendar rules</param>
        /// <returns>
        /// The item change.
        /// </returns>
        private ItemChangeType CreateItemChangeForUpdatingReminder(ItemIdType itemId, NotificationRuleItem reminderRule, NotificationRuleItem joinNowRule, TimeZoneInfo timeZoneInfo)
        {
            Debug.Assert(itemId != null, "itemId != null");
            Debug.Assert(reminderRule != null || joinNowRule != null, "reminderRule != null || joinNowRule != null");

            // Create the reminder item from the rules
            ReminderItem reminderItem = NotificationRuleItem.CreateCalendarReminderItem(reminderRule, joinNowRule, timeZoneInfo);
            string reminderPropertyValue = EnhancedRemindersHelpers.SerializeReminderItem(Context, reminderItem);

            // Create the change description for adding the reminder
            SetItemFieldType setItemReminder = CreateItemChangeDescription(ExchangeHelpers.GetExtendedPropertyPathForReminder(), reminderPropertyValue);

            ItemChangeDescriptionType itemChangeReminderRuleId = null;

            if (reminderRule != null)
            {
                // Create the change description for adding the reminder rule ID
                itemChangeReminderRuleId = CreateItemChangeDescription(ExchangeHelpers.GetExtendedPropertyPathForReminderRuleId(), reminderRule.Id.ToString("D"));
            }
            else
            {
                // Create the change description for deleting the reminder rule ID
                itemChangeReminderRuleId = new DeleteItemFieldType();
                itemChangeReminderRuleId.Item = ExchangeHelpers.GetExtendedPropertyPathForReminderRuleId();
            }

            ItemChangeDescriptionType itemChangeJoinNowRuleId = null;

            if (joinNowRule != null)
            {
                // Create the change description for adding the JoinNow rule ID
                itemChangeJoinNowRuleId = CreateItemChangeDescription(ExchangeHelpers.GetExtendedPropertyPathForJoinNowRuleId(), joinNowRule.Id.ToString("D"));
            }
            else
            {
                // Create the change description for deleting the JoinNow rule ID
                itemChangeJoinNowRuleId = new DeleteItemFieldType();
                itemChangeJoinNowRuleId.Item = ExchangeHelpers.GetExtendedPropertyPathForJoinNowRuleId();
            }

            Debug.Assert(itemChangeReminderRuleId != null && itemChangeJoinNowRuleId != null,
                "itemChangeReminderRuleId != null && itemChangeJoinNowRuleId != null");

            ItemChangeType itemChange = new ItemChangeType();
            itemChange.Item = itemId;
            itemChange.Updates = new ItemChangeDescriptionType[] { setItemReminder, itemChangeReminderRuleId, itemChangeJoinNowRuleId };

            return itemChange;
        }
    }

    /// <summary>
    /// This handles the task of setting the reminder delivered custom property on a
    /// calendar item.
    /// </summary>
    internal class SetReminderDeliveredTask : UpdateItemTask
    {
        /// <summary>
        /// ctor
        /// </summary>
        /// <param name="taskContext">Task context</param>
        /// <param name="client">WCF proxy for communicating with EWS</param>
        /// <param name="impersonation">Exchange impersonation info</param>
        /// <param name="emailAddress">E-mail address of the user associated with this request</param>
        /// <param name="itemId">The item on which to set the reminder</param>
        /// <param name="deliveryTime">The time the notification was delivered.</param>
        internal SetReminderDeliveredTask(TaskContext taskContext, ExchangeServicePortType client, ExchangeImpersonationType impersonation, string emailAddress, ItemIdType itemId, DateTime deliveryTime)
            : base(taskContext, client, impersonation, emailAddress)
        {
            Debug.Assert(itemId != null, "itemId != null");

            _changes = new ItemChangeType[] { CreateItemChangeForUpdatingReminder(itemId, deliveryTime) };
        }

        /// <summary>
        /// Create the item change description for adding a rules-based reminder derived from the specified rules to the 
        /// specified calendar item.
        /// </summary>
        /// <param name="itemId">The item to be updated</param>
        /// <param name="deliveryTime">The time the notification was delivered.</param>
        /// <returns>
        /// The item change.
        /// </returns>
        private static ItemChangeType CreateItemChangeForUpdatingReminder(ItemIdType itemId, DateTime deliveryTime)
        {
            Debug.Assert(itemId != null, "itemId != null");

            // Create the change description for setting the delivery time.
            SetItemFieldType setItemDeliveryTime = CreateItemChangeDescription(ExchangeHelpers.GetExtendedPropertyPathForReminderDelivered(), deliveryTime.Ticks.ToString(CultureInfo.InvariantCulture));

            ItemChangeType itemChange = new ItemChangeType();
            itemChange.Item = itemId;
            itemChange.Updates = new ItemChangeDescriptionType[] { setItemDeliveryTime };

            return itemChange;
        }
    }
}
